using System;
using System.Collections;
using System.Collections.Generic;

namespace Framework
{
	public static class RndUtil
	{
		private static Random m_random = new Random(unchecked((int)DateTime.Now.Ticks));

		#region Next
		/// <summary> Next
		/// <p>Returns a nonnegative random number greater then / equal to zero and less then maximum size of int (2,147,483,647)</p>
		/// </summary>
		/// 
		/// <returns> Random Number</returns>
		public static int Next()
		{		    
			return m_random.Next();
		}

		/// <summary> Next
		/// <p>Returns a nonnegative random number greater then / equal to zero and less than Max.</p>
		/// </summary>
		/// 
		/// <param name="max">Maximum random number</param>
		/// <returns> Random Number between 0 and Max</returns>
		public static int Next(int max)
		{
			return m_random.Next(max);
		}

		/// <summary> Next
		/// <p>Returns a random number greater then / equal to Min and less than Max.</p>
		/// <p>Next( 5,10 ) will return 5, 6, 7, 8 or 9</p>
		/// </summary>
		/// 
		/// <param name="min">Minimum random number</param>
		/// <param name="max">Maximum random number</param>
		/// <returns> Random Number between Min and Max (Exclusive)</returns>
		public static int Next(int min, int max)
		{
			return m_random.Next(min, max);
		}
		#endregion

		#region Range
		/// <summary> Range
		/// <p>Similar to Next( min, max ), except that it will return upto and inclusive of max.</p>
		/// <p>Range( 5,10 ) will return 5, 6, 7, 8, 9 or 10</p>
		/// </summary>
		/// 
		/// <param name="min">Minimum random number</param>
		/// <param name="max">Maximum random number</param>
		/// <returns> Random Number between Min and Max (Inclusive)</returns>
		public static int Range(int min, int max)
		{
			return m_random.Next(min, max + 1);
		}
		#endregion

        #region RandomBool
        /// <summary> 
        /// <p>Returns a random boolean value</p>
        /// </summary>
        /// 
        /// <returns> Random string </returns>
        public static bool Bool()
        {
            return Range(0, 1) == 0;
        }
        #endregion

        #region RandomElement
        /// <summary> 
		/// <p>Returns a random string from an array of string</p>
		/// </summary>
		/// 
		/// <param name="values">String array to lookup from</param>
		/// <returns> Random string </returns>
		public static T RandomElement<T>(T[] values)
		{
			return values[Next(values.Length)];
		}

		public static T RandomElement<T>(IList<T> values)
		{
			if (values == null || values.Count == 0)
			{
				return default(T);
			}

			return values[Next(values.Count)];
		}
		#endregion

        #region RandomString
        /// <summary> 
        /// <p>Returns a random string from an array of string</p>
        /// </summary>
        /// 
        /// <param name="values">String array to lookup from</param>
        /// <returns> Random string </returns>
        public static string RandomString(string[] values)
        {
            return values[Next(values.Length)];
        }
        /// <summary> 
        /// <p>Returns a random string from an array of string</p>
        /// </summary>
        /// 
        /// <param name="values">String list to lookup from</param>
        /// <returns> Random string </returns>
        public static string RandomString(List<string> values)
        {
            return values == null || values.Count == 0 ? string.Empty : values[Next(values.Count)];
        }
        #endregion

        #region RandomStrings
        /// <summary>
        /// <p>Returns a number of random strings from the supplied values equal to the provided count</p>
        /// </summary>
        /// <param name="values">Values to select from (randomize)</param>
        /// <param name="count">Number strings to return</param>
        /// <returns>A number of random items from values equal to the provided count</returns>
        public static List<string> RandomStrings(List<string> values, int count)
        {           
            if (values == null || count > values.Count) 
            {
                return null;
            }            
             
            var selectedIndexes = new List<int>(); 
            var results = new List<string>();

            for (var index = 0; index < count; index++)
            {
                var randomIndex = Next(values.Count);
                while (selectedIndexes.Contains(randomIndex))
                {
                    randomIndex = Next(values.Count);
                }
                selectedIndexes.Add(randomIndex);
                results.Add(values[randomIndex]);
            }                      
            return results;
        }
        #endregion

        #region RandomIntList
        /// <summary>
        /// 	<p>Returns a list of random integers</p>
        /// </summary>
        /// <param name="count">The count.</param>
        /// <param name="rangeBegin">The range begin.</param>
        /// <param name="rangeEnd">The range end.</param>
        /// <param name="isUnique">if set to <c>true</c> [is unique].</param>
        /// <returns></returns>
        public static List<int> RandomIntList(int count, int rangeBegin, int rangeEnd, bool isUnique = false)
        {
            var result = new List<int>(count);

            if (isUnique)
            {
                if ((rangeEnd - rangeBegin) < count)
                {
                    throw new ArgumentException("Count is bigger then the range of unique numbers requested");
                }

                var intPool = new List<int>(count);

                for (var i = rangeBegin; i <= rangeEnd; i++)
                {
                    intPool.Add(i);
                }

                for (var i = 0; i < count; i++)
                {
                    var selectedIndex = Next(intPool.Count);

                    result.Add(intPool[selectedIndex]);

                    intPool.RemoveAt(selectedIndex);
                }
            }
            else
            {
                for (var i = 0; i < count; i++)
                {
                    result.Add(Range(rangeBegin, rangeEnd));
                }
            }

            return result;
        }
        #endregion

		#region ListElement
		/// <summary> Get element value from an array.
		/// <p> Returns a random element from any object that implements IList, if the IList is null or empty then null will be returned.</p>
		/// </summary>
		/// 
		/// <param name="list">Object implementing IList to return value from</param>
		/// <returns> Random object</returns>
		public static Object ListElement(IList list)
		{
			if (list == null || list.Count == 0)
			{
				return null;
			}

			return list[Next(list.Count)];
		}
		#endregion

		#region PercentageGroup
		/// <summary> Randomly returns a group position using percentile weightings.
		/// <pre> 
		/// Example:
		/// 
		/// int groupNo = RndUtil.PercentageGroup( new int[] { 20, 10, 15, 55 } );
		/// 
		/// groupNo will return 0 - 20%, 1 - 10%, 2 - 15% and 3 - 55%
		/// </pre>
		/// </summary>
		/// 
		/// <param name="percentGroups">Weighted Groups</param>
		/// <returns>Group No</returns>
		public static int PercentageGroup(int[] percentGroups)
		{
			int totalPercent = 0;
			int result = -1;
			int rndPercent = Range(1, 100);

			for (int i = 0; i < percentGroups.Length; i++)
			{
				totalPercent += percentGroups[i];

				if (result == -1 && rndPercent <= totalPercent)
				{
					result = i;
				}
			}

			if (totalPercent != 100)
			{
				throw new ArgumentOutOfRangeException("Random percentage groups must total 100%");
			}

			return result;
		}
		#endregion

		#region RandomDate
		/// <summary> 
		/// Returns a random date within a random <var>range</var> of days
		/// with a plus/minus <var>offset</var> of days applied.
		/// </summary>
		/// 
		/// <param name="range">Random day count maximum to add to today</param>
		/// <param name="offset">plus/minus no of days applied.</param>
		/// <returns>DateTime</returns>
		public static DateTime RandomDate(int range, int offset)
		{
			return DateTime.Now.AddDays(Next(range) + offset);
		}
		#endregion

		#region RandomCharList
		public static string RandomCharList(int length, char[] validCharCodes)
		{
			char[] result = new char[length];

			for (int i = 0; i < length; i++)
			{
				result[i] = validCharCodes[Next(validCharCodes.Length)];
			}

			return new String(result);
		}
		#endregion

		#region RandomAlpha, RandomAlphaNumeric

	    // *********************************************************************************
	    // Letters and Numbers
	    // *********************************************************************************
		private static readonly char[] ALPHA_NUMERIC_LIST = new char[] 
        { 
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 
            'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' 
        };

        private static readonly char[] ALPHA_LOWER_NUMERIC_LIST = new char[] 
        { 
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 
            'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
        };


        private static readonly char[] ALPHA_UPPER_NUMERIC_LIST = new char[] 
        { 
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' 
        };

	    // *********************************************************************************
	    // Letters Only
	    // *********************************************************************************
        private static readonly char[] ALPHA_LIST = new char[] 
        { 
            'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' 
        };

        private static readonly char[] ALPHA_LOWER_LIST = new char[] 
        { 
            'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
        };

        private static readonly char[] ALPHA_UPPER_LIST = new char[] 
        { 
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' 
        };

		public static string RandomAlpha(int length)
		{
			return RandomCharList(length, ALPHA_LIST);
		}

        public static string RandomAlphaLower(int length)
        {
            return RandomCharList(length, ALPHA_LOWER_LIST);
        }
        
        public static string RandomAlphaUpper(int length)
        {
            return RandomCharList(length, ALPHA_UPPER_LIST);
        }
        
        public static string RandomAlphaNumeric(int length)
        {
            return RandomCharList(length, ALPHA_NUMERIC_LIST);
        }

        public static string RandomAlphaUpperNumeric(int length)
        {
            return RandomCharList(length, ALPHA_UPPER_NUMERIC_LIST);
        }

        public static string RandomAlphaLowerNumeric(int length)
        {
            return RandomCharList(length, ALPHA_LOWER_NUMERIC_LIST);
        }
        #endregion

		#region GuidValue
		/// <summary>
		/// Get a Guid value as a string in using format: D - 32 digits separated by hyphens: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
		/// </summary>
		/// <returns>Guid: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx</returns>
		public static string GuidValue()
		{
			return GuidValue("D");
		}

		/// <summary>
		/// Get a Guid value as a string in using format: N - 32 digits: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
		/// </summary>
		/// <returns>Guid: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</returns>
		public static string GuidValuePlain()
		{
			return GuidValue("N");
		}

		/// <summary>
		/// Get a Guid value as a string in using format: B - 32 digits separated by hyphens, enclosed in brackets: {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
		/// </summary>
		/// <returns>Guid: {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}</returns>
		public static string GuidValueBrace()
		{
			return GuidValue("B");
		}

		/// <summary>
		/// Get a Guid value as a string in using Brace Format
		/// </summary>
		/// <returns>Guid: {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}</returns>
		public static string GuidValueDb()
		{
			return GuidValue("B");
		}

		/// <summary>
		/// Get a Guid value as a string in using format: P - 32 digits separated by hyphens, enclosed in parentheses: (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
		/// </summary>
		/// <returns>Guid: (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)</returns>
		public static string GuidValueParentheses()
		{
			return GuidValue("P");
		}

		/// <summary>
		/// Get a Guid value as a string in specified format
		/// 
		/// N - 32 digits: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
		/// D - 32 digits separated by hyphens: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
		/// B - 32 digits separated by hyphens, enclosed in brackets: {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
		/// P - 32 digits separated by hyphens, enclosed in parentheses: (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
		/// </summary>
		/// <param name="format">N, D, B or P</param>
		/// <returns></returns>
		public static string GuidValue(string format)
		{
			return Guid.NewGuid().ToString(format);
		}
		#endregion
	}
}
